Unity2D游戏
一、图片
1 人物
Pixels Per Unit:每个单元的像素。值越大图显示越小。
Pivot:中心点。
Default——Max Size:一般选宽高中的大的值的接近值。例如290 * 500 选 512。
2 问题
- 图片太小:调节图片的Pixels Per Unit。
二、组件
1 Sprite Renderer
Sorting Layer:排序层。可以添加层,越在前面的层显示越在屏幕的前面。一般用Default设置Order in Layer既可。
Order in Layer:层。值越大显示越在屏幕前。
2 Camera
Projection:Orthographic。
size:相机中心到边缘的unit个数。
三、动画
1 人物动画
新建动画后,直接旋中所有图片拖拽激到animation中,再点击右边三个小点的Show Sample Rate(每秒帧数)进行调整既可。
2 Chinemachine
安装:
- Window——Package Manager——Packages:Unity Registry——Chinemachine——install
- Hierarchy右键——Chinemachine——2D Camera。(此时主摄像机由CM vcam1控制)
CM vcam1:
- Follow:跟随对象。
- Lens——Ortho Size:网格大小。
- Body:
- Damping:当人物离中心点 n 距离摄像机才开始移动。
- Screen:人物所处位置。
- Dead Zone:在范围中相机完全不移动。
- Soft Zone:蓝色区域大小。玩家可移动的区域。红色区域玩家不可到达。
- Bias:蓝色区域移动。
边界:
- Hierarchy右键——Create——Create Empty
- Add Component——Polygon Collider 2D
- Points——Path——Element0——Size:4
- 拖动边界为整个地图。
- CM vcam1——Extensions——Add Extension——Cinemachine Confiner
- 拖入空物体至Bounding Shape 2D
四、脚本
1 属性
public float speed; public int enemyLife;
public Vector3 targetPosition, originPosition, terminalPosition;
private bool isAfterBattleCheck;
private BoxCollider2D box; private Sprite private Animator animator; public GameObject attackCollider;
private GameObject player;
|
2 移动
void FixedUpdate() { Move(); }
private void Move() { float leftRight = Input.GetAxisRaw("Horizontal"); float upDown = Input.GetAxisRaw("Vertical"); if (leftRight > 0) { transform.localScale = new Vector3(1f, 1f, 1f); } else if (leftRight < 0) { transform.localScale = new Vector3(-1f, 1f, 1f); } transform.Translate(leftRight * Time.fixedDeltaTime * speed, upDown * Time.fixedDeltaTime * speed, 0); animator.SetFloat("Run", Mathf.Max(Mathf.Abs(leftRight), Mathf.Abs(upDown))); }
public class PlayerControl : MonoBehaviour { private Rigidbody2D rb;
[SerializeField] private float moveSpeed; [SerializeField] private float jumpForce; private float xInput;
void Start() { rb = GetComponent<Rigidbody2D>(); } void Update() { Movement(); CheckInput(); }
private void CheckInput() { xInput = Input.GetAxisRaw("Horizontal");
if (Input.GetKeyDown(KeyCode.Space)) { Jump(); } }
private void Movement() { rb.velocity = new Vector2(xInput * moveSpeed, rb.velocity.y); }
private void Jump() { rb.velocity = new Vector2(rb.velocity.x, jumpForce); } }
|
3 设置人物属性
public class ShowInInspector : MonoBehaviour { [System.Serializable] public struct Attributes { public int HP; public int MP; }
[System.Serializable] public struct Character { public int skinID; public int characterID; } public Attributes attribute;
public Character character; [Space] }
|
五、场景
1 Tile Palette
创建 Palette:
Window——2D——Tile Palette——Create New Palette。
拖入图片,图片转换格式后存入一个新文件夹中。
Edit Palette:
- S:选中物体
- M:移动物体
- B:选取后直接移动到场景中。
- I:先选中,后按左键复制物体。
- D:擦去场景中的错误。
使用 Palette:
- Hierarchy右键——Create——2D Object——Tilemap
- 在Palette中选择Active Tilemap
- 在Palette中安B选择物体移动到场景中。
碰撞器:
- 添加Tilemap Collider 2D。(该项比较消耗资源,需要进行后续设置)
- 添加Composite Collider 2D,会自动添加Rigidbody 2D。
- Rigidbody 2D——Body Type——Static。
- Tilemap Collider 2D——Used By Composite。
2 边界
限定边界用碰撞器。当玩家碰到底部物件(通过底部物件的名字判断)时直接杀死玩家。
3 平台
可穿越平台:
- 设置一个父空物件,将地形作为该父物件的子物件。
- 给父物件添加Platform Effector 2D和Box Collider,Box Collider选择Used By Effector
- 设置Box Collider 2D的大小,使其大小仅仅为地面区域。
Platform Effector 2D——Surface Arc:作用弧度。
public class PlatformMove : MonoBehaviour { public Vector3 originPosition, targetPosition, terminalPosition; public float moveSpeed;
private void Start() { targetPosition = terminalPosition; originPosition = transform.position; }
private void Update() { Move(); }
private void Move() { if (transform.position == targetPosition) { if (targetPosition == originPosition) { targetPosition = terminalPosition; } else { targetPosition = originPosition; } } transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime); } }
private void OnTriggerEnter2D(Collider2D collision) { if(collision.tag == "AirPlatform") { playerScript.transform.parent = collision.transform; } } private void OnTriggerExit2D(Collider2D collision) { if(collision.tag == "AirPlatform") { playerScript.transform.parent = null; } }
|
4 UI
- Canvas——Render Mode——Screen Space-Camera
- 拖入主摄像机
- Order in Layer设置大于场景Order in Layer
Canvas Renderer 的 “Cull Transparent Mesh” 属性可以根据 UI 元素的透明度进行渲染剔除,以提高渲染性能。需要注意的是,这个属性只会影响透明的部分,对不透明的像素没有影响。
六、设计
1 路线设计
根路线——多条子路线——多个路线点。
选择时,从跟路线出发,选择多条子路线中的一条以此保证随机性。
这些对象都是空物体。
七、打包
手机型号查询:
Package Manager——Device Simulator
Simulator——将game改成Simulator
- 查看手机的Safe Area
背景有留白:
多创建两张背景,将背景进行拼接,从而解决留白。
public class SafeAreaPanel : MonoBehaviour { private RectTransform panel;
void Start() { panel = GetComponent<RectTransform>();
Vector2 safeAreaMinPosition = Screen.safeArea.position; Vector2 safeAreaMaxPosition = Screen.safeArea.position + Screen.safeArea.size;
safeAreaMinPosition.x = safeAreaMinPosition.x / Screen.width; safeAreaMinPosition.y = safeAreaMinPosition.y / Screen.height;
safeAreaMaxPosition.x = safeAreaMaxPosition.x / Screen.width; safeAreaMaxPosition.y = safeAreaMaxPosition.y / Screen.height;
panel.anchorMin = safeAreaMinPosition; panel.anchorMax = safeAreaMaxPosition; } }
|
八、问题
1 物件翻转
刚体中设置固定Z轴。
2 墙壁
卡墙:给玩家的Rigidbody 2D加Material。Material的Friction设置为0。
靠墙跳无法触发跳跃动画:父物体下加一个空物体,空物体加上Box Collider 2D,选上Is Trigger,拉到人物底部,调整大小为宽度同人物宽,高度尽可能小。
public class PlayerTrigger : MonoBehaviour { private PlayerControl playerScript; void Start() { playerScript = GetComponentInParent<PlayerControl>(); }
private void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Ground") { playerScript.canJump = true; playerScript.animator.SetBool("Jump", false); } } }
|
3 碰撞器
碰撞器位置:
碰撞器休眠:
当玩家不移动时,碰撞器会休眠。此时再攻击会无法触发攻击效果。
解决方法1:Rigidbody 2D——Sleeping Mode——Never Sleep(注:该设置十分消耗性能,一般只给玩家设置即可)
解决方法2:玩家的判断攻击的子物体中加上Rigidbody 2D——Body Type——Kinematic。此时玩家不需要再设置为Never Sleep了。因为Collider中的Info的判定信息变成了攻击的子物体而不是玩家。
无法重复碰撞:
摄像头碰撞器:
触发器重叠:
4 重复动画播放
设置了trigger的属性可能会重复播放动画。
解决方法:需要在动画的最后一帧加上事件,事件绑定函数重置触发器。
private void Update() { float swordAttack = Input.GetAxisRaw("Sword"); if (swordAttack > 0) { animator.SetTrigger("Attack"); isAttack = true; canJump = false; } }
public void SetIsAttackFalse() { isAttack = false; canJump = true; animator.ResetTrigger("Attack"); }
|
5 受伤
在播放受伤动画时输入的值会被记录,导致动画结束后会立刻执行响应动作。(例如:被击飞时按了闪现,落地后会立刻闪现)
受伤后不会无敌,会重复受伤。
解决方法:在相应条件后面加上&& isHurt == false。例如:if (jump > 0 && canJump == true && isHurt == false)
6 远程武器被挡
武器设定碰撞为if (collision != null && collision.tag != "Player")。当玩家使用远程武器时,武器会被敌人设定的AttackCollider空物体给摧毁。
解决方法:设定Layer,玩家攻击为PlayerAttack,敌人攻击为EnemyAttack。然后到Edit——Project Setting——Physics 2D,然后把两者的作用关系的勾取消掉即可。
7 背景
背景不随人物移动。
解决方法:拖动背景作为主摄像机的子物体。
8 寻路穿墙
某些敌方单位会跟随玩家移动,此时会发生穿墙,浮空等BUG。
解决方法:
- 在地方单位两侧设置停止点,停止点为空物体,加上触发器。
- 给地方单位加上刚体,设置为Body Type——Kinematic。
9 寻找物件
canvas = GameObject.Find("/Canvas").GetComponent<Canvas>();
|
10 子弹判定
因为速度太快,用碰撞和触发不一定会触发,所以要用射线判定。
图层:设置哪一层射线就检测哪一层。可以给敌人专门做一个层。
using UnityEngine;
public class Bullet : MonoBehaviour { public float speed = 50f; public float maxDistance = 300f; public LayerMask hitLayers;
private void Update() { float distance = speed * Time.deltaTime;
RaycastHit hit; if (Physics.Raycast(transform.position, transform.forward, out hit, distance, hitLayers)) { Debug.Log("射中了:" + hit.collider.gameObject.name);
}
transform.Translate(Vector3.forward * distance);
if (Vector3.Distance(transform.parent.position, transform.position) >= maxDistance) { Destroy(gameObject); } } }
|
九、优化
1 资源重复加载
为节约性能,把需要重复加载的内容放在ResourcesManager类中。然后在该类中写静态构造函数和静态方法。静态构造函数负责读取资源到类中。静态函数负责被其他类调用返回指定资源。
public class ResourcesManager : MonoBehaviour { private static Dictionary<int, Sprite> spriteDict; static ResourcesManager() { spriteDict = new Dictionary<int, Sprite>(); SpriteAtlas sprites = Resources.Load<SpriteAtlas>("People"); foreach (Sprite sprite in sprites.GetPackables()) { int id = int.Parse(sprite.name); spriteDict.Add(id, sprite); } } public static Sprite LoadSprite(int number) { return spriteDict[number]; } }
|